#include "getopt.h"
#include <iostream>
#include <tuple>
#include <fstream>
#include <unordered_set>

#include <string.h>

#include <public/utils.h>
#include <graph.priv.h>
#include <config-fnt.h>

/* Options */
static const char* opts=":hv";
static const struct option opts_long[]=
{
	{"help", 0, nullptr, 'h'},
	{"version", 0, nullptr, 'v'},
	{nullptr, 0, nullptr, 0}
};

/* Print version information and exit */
void version(const char* name) {
	std::cout<<name<<" (" PACKAGE_NAME_LONG ") " PACKAGE_VERSION "\n";
	std::cout<<"Copyright (C) 2015-2016 " PACKAGE_ORG "\n";
	std::cout<<"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\n";
	std::cout<<"Written by: " PACKAGE_AUTHORS "\n";
	exit(0);
}
/* Print usage information and exit */
void usage(const char* name, int ecode) {
	if(ecode) {
		std::cerr<<"Try '"<<name<<" --help' for more information.\n";
		exit(ecode);
	}
	std::cout<<"Usage: "<<name<<" [OPTION]... SWC_FILE FNT_FILE\n";
	std::cout<<
		"Convert tracing data in SWC format to .fnt or .fntz files.\n\n"
		"Options:\n"
		"  -h, --help       [OPTIONAL] Print this help, and do nothing else.\n"
		"  -v, --version    [OPTIONAL] Print version information and exit.\n\n"
		"SWC_FILE is a file in SWC format. Output is written to FNT_FILE, with\n"
		"extension fnt or .fntz.\n\n"
		"Report bugs to <" PACKAGE_BUGREPORT ">\n"
		PACKAGE_NAME_LONG " home page: <" PACKAGE_URL ">\n";
	exit(ecode);
}

int main(int argc, char* argv[]) {

	int opt;
	int prev_optind=optind;
	while((opt=getopt_long(argc, argv, opts, opts_long, nullptr))!=-1) {
		try {
			switch(opt) {
				case '?':
					throwError("unrecognized option '", argv[prev_optind], "'\n");
				case ':':
					throwError("option '", argv[prev_optind], "' requires an argument\n");
				case 'h':
					usage(argv[0], 0);
				case 'v':
					version(argv[0]);
				default:
					throwError("Unexpected error.\n");
			}
		} catch(std::exception& e) {
			std::cerr<<argv[0]<<": "<<e.what();
			usage(argv[0], -1);
		}
		prev_optind=optind;
	}

	std::vector<std::tuple<int64_t, int16_t, double, double, double, double, int64_t>> swc{};

	try {
		if(argc-optind!=2)
			throwError("Wrong number of positional arguments.");

		std::ifstream ifs{argv[optind]};
		if(!ifs)
			throwError("Failed to open input file.");
		ifs.precision(13);
		ifs.setf(std::ios::scientific);

		std::unordered_set<int64_t> roots;
		std::string line;
		while(std::getline(ifs, line)) {
			if(line.size()==0) continue;
			if(line[0]=='#') {
				if(line.size()>6 && strncmp("Gapr!", &line[1], 5)==0) {
					int64_t id;
					char* eptr;
					errno=0;
					id=strtol(&line[6], &eptr, 10);
					std::size_t n=eptr-&line[0];
					if(errno==0 && line.size()>=n+5 && strncmp("@root", &line[n], 5)==0 && (line.size()==n+5 || line[n+5]=='=')) {
						roots.insert(id);
					}
				}
				continue;
			}
			std::istringstream iss{line};
			iss.precision(13);
			iss.setf(std::ios::scientific);

			int64_t id, par;
			int16_t type;
			double x, y, z, r;
			iss>>id>>type>>x>>y>>z>>r>>par;
			if(!iss)
				throwError("Failed to parse line: ", line);
			swc.push_back(std::make_tuple(id, type, x, y, z, r, par));
		}
		if(!ifs.eof())
			throwError("Failed to read file.");
		if(!roots.empty()) {
			for(auto& n: swc) {
				if(roots.find(std::get<0>(n))!=roots.end())
					std::get<1>(n)=1;
				else if(std::get<1>(n)==1)
					std::get<1>(n)=0;
			}
		}
	} catch(std::exception& e) {
		std::cerr<<argv[0]<<": "<<e.what();
		usage(argv[0], -1);
	}

	if(swc.size()==0) {
		std::cerr<<"Empty SWC file.\n";
		return 0;
	}

	try {
		Graph gr=GraphPriv::alloc();
		auto gp=GraphPriv::get(gr);
		gp->fromSwc(swc);
		gp->saveFnt(argv[optind+1], {FNT_MAGIC, "NONE"});
		GraphPriv::free(gr);
	} catch(std::exception& e) {
		std::cerr<<"Error while saving FNT file: "<<e.what();
		return -1;
	}

	return 0;
}

